/*******************************************************************************
 * Copyright (c) 2009 Dmitry Grushin <dgrushin@gmail.com>.
 * 
 * This file is part of GridMe.
 * 
 * GridMe is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GridMe is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GridMe.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Dmitry Grushin <dgrushin@gmail.com> - initial API and implementation
 ******************************************************************************/
package statemachine.diagram.part;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.ui.URIEditorInput;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.diagram.core.DiagramEditingDomainFactory;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.AbstractDocumentProvider;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.DiagramDocument;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.IDiagramDocument;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.IDiagramDocumentProvider;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.IDocument;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.internal.EditorStatusCodes;
import org.eclipse.gmf.runtime.diagram.ui.resources.editor.internal.util.DiagramIOUtil;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.emf.core.resources.GMFResourceFactory;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.part.FileEditorInput;

/**
* @generated
*/
public class StatemachineDocumentProvider extends AbstractDocumentProvider
    implements IDiagramDocumentProvider
{

  /**
  * @generated
  */
  protected ElementInfo createElementInfo(Object element) throws CoreException
  {
    if(false == element instanceof FileEditorInput
        && false == element instanceof URIEditorInput)
    {
      throw new CoreException(
          new Status(
              IStatus.ERROR,
              StatemachineDiagramEditorPlugin.ID,
              0,
              NLS
                  .bind(
                      Messages.StatemachineDocumentProvider_IncorrectInputError,
                      new Object[] {
                          element,
                          "org.eclipse.ui.part.FileEditorInput", "org.eclipse.emf.common.ui.URIEditorInput" }), //$NON-NLS-1$ //$NON-NLS-2$ 
              null));
    }
    IEditorInput editorInput = (IEditorInput) element;
    IDiagramDocument document = (IDiagramDocument) createDocument(editorInput);

    ResourceSetInfo info = new ResourceSetInfo(document, editorInput);
    info.setModificationStamp(computeModificationStamp(info));
    info.fStatus = null;
    return info;
  }

  /**
  * @generated
  */
  protected IDocument createDocument(Object element) throws CoreException
  {
    if(false == element instanceof FileEditorInput
        && false == element instanceof URIEditorInput)
    {
      throw new CoreException(
          new Status(
              IStatus.ERROR,
              StatemachineDiagramEditorPlugin.ID,
              0,
              NLS
                  .bind(
                      Messages.StatemachineDocumentProvider_IncorrectInputError,
                      new Object[] {
                          element,
                          "org.eclipse.ui.part.FileEditorInput", "org.eclipse.emf.common.ui.URIEditorInput" }), //$NON-NLS-1$ //$NON-NLS-2$ 
              null));
    }
    IDocument document = createEmptyDocument();
    setDocumentContent(document, (IEditorInput) element);
    setupDocument(element, document);
    return document;
  }

  /**
  * Sets up the given document as it would be provided for the given element. The
  * content of the document is not changed. This default implementation is empty.
  * Subclasses may reimplement.
  * 
  * @param element the blue-print element
  * @param document the document to set up
  * @generated
  */
  protected void setupDocument(Object element, IDocument document)
  {
    // for subclasses
  }

  /**
  * @generated
  */
  private long computeModificationStamp(ResourceSetInfo info)
  {
    int result = 0;
    for(Iterator it = info.getResourceSet().getResources().iterator(); it
        .hasNext();)
    {
      Resource nextResource = (Resource) it.next();
      IFile file = WorkspaceSynchronizer.getFile(nextResource);
      if(file != null)
      {
        if(file.getLocation() != null)
        {
          result += file.getLocation().toFile().lastModified();
        }
        else
        {
          result += file.getModificationStamp();
        }
      }
    }
    return result;
  }

  /**
  * @generated
  */
  protected IDocument createEmptyDocument()
  {
    DiagramDocument document = new DiagramDocument();
    document.setEditingDomain(createEditingDomain());
    return document;
  }

  /**
  * @generated
  */
  private TransactionalEditingDomain createEditingDomain()
  {
    TransactionalEditingDomain editingDomain = DiagramEditingDomainFactory
        .getInstance().createEditingDomain();
    editingDomain.setID("com.googlecode.gridme.simstate.diagram.EditingDomain"); //$NON-NLS-1$
    final NotificationFilter diagramResourceModifiedFilter = NotificationFilter
        .createNotifierFilter(editingDomain.getResourceSet()).and(
            NotificationFilter.createEventTypeFilter(Notification.ADD)).and(
            NotificationFilter.createFeatureFilter(ResourceSet.class,
                ResourceSet.RESOURCE_SET__RESOURCES));
    editingDomain.getResourceSet().eAdapters().add(new Adapter()
    {

      private Notifier myTarger;

      public Notifier getTarget()
      {
        return myTarger;
      }

      public boolean isAdapterForType(Object type)
      {
        return false;
      }

      public void notifyChanged(Notification notification)
      {
        if(diagramResourceModifiedFilter.matches(notification))
        {
          Object value = notification.getNewValue();
          if(value instanceof Resource)
          {
            ((Resource) value).setTrackingModification(true);
          }
        }
      }

      public void setTarget(Notifier newTarget)
      {
        myTarger = newTarget;
      }

    });

    return editingDomain;
  }

  /**
  * @generated
  */
  protected void setDocumentContent(IDocument document, IEditorInput element) throws CoreException
  {
    IDiagramDocument diagramDocument = (IDiagramDocument) document;
    TransactionalEditingDomain domain = diagramDocument.getEditingDomain();
    if(element instanceof FileEditorInput)
    {
      IStorage storage = ((FileEditorInput) element).getStorage();
      Diagram diagram = DiagramIOUtil.load(domain, storage, true,
          getProgressMonitor());
      document.setContent(diagram);
    }
    else if(element instanceof URIEditorInput)
    {
      URI uri = ((URIEditorInput) element).getURI();
      Resource resource = null;
      try
      {
        resource = domain.getResourceSet().getResource(uri.trimFragment(),
            false);
        if(resource == null)
        {
          resource = domain.getResourceSet().createResource(uri.trimFragment());
        }
        if(!resource.isLoaded())
        {
          try
          {
            Map options = new HashMap(GMFResourceFactory
                .getDefaultLoadOptions());
            // @see 171060 
            // options.put(org.eclipse.emf.ecore.xmi.XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, Boolean.TRUE);
            resource.load(options);
          }
          catch(IOException e)
          {
            resource.unload();
            throw e;
          }
        }
        if(uri.fragment() != null)
        {
          EObject rootElement = resource.getEObject(uri.fragment());
          if(rootElement instanceof Diagram)
          {
            document.setContent((Diagram) rootElement);
            return;
          }
        }
        else
        {
          for(Iterator it = resource.getContents().iterator(); it.hasNext();)
          {
            Object rootElement = it.next();
            if(rootElement instanceof Diagram)
            {
              document.setContent((Diagram) rootElement);
              return;
            }
          }
        }
        throw new RuntimeException(
            Messages.StatemachineDocumentProvider_NoDiagramInResourceError);
      }
      catch(Exception e)
      {
        CoreException thrownExcp = null;
        if(e instanceof CoreException)
        {
          thrownExcp = (CoreException) e;
        }
        else
        {
          String msg = e.getLocalizedMessage();
          thrownExcp = new CoreException(new Status(IStatus.ERROR,
              StatemachineDiagramEditorPlugin.ID, 0, msg != null ? msg
                  : Messages.StatemachineDocumentProvider_DiagramLoadingError,
              e));
        }
        throw thrownExcp;
      }
    }
    else
    {
      throw new CoreException(
          new Status(
              IStatus.ERROR,
              StatemachineDiagramEditorPlugin.ID,
              0,
              NLS
                  .bind(
                      Messages.StatemachineDocumentProvider_IncorrectInputError,
                      new Object[] {
                          element,
                          "org.eclipse.ui.part.FileEditorInput", "org.eclipse.emf.common.ui.URIEditorInput" }), //$NON-NLS-1$ //$NON-NLS-2$ 
              null));
    }
  }

  /**
  * @generated
  */
  public long getModificationStamp(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      return computeModificationStamp(info);
    }
    return super.getModificationStamp(element);
  }

  /**
  * @generated
  */
  public boolean isDeleted(Object element)
  {
    IDiagramDocument document = getDiagramDocument(element);
    if(document != null)
    {
      Resource diagramResource = document.getDiagram().eResource();
      if(diagramResource != null)
      {
        IFile file = WorkspaceSynchronizer.getFile(diagramResource);
        return file == null || file.getLocation() == null
            || !file.getLocation().toFile().exists();
      }
    }
    return super.isDeleted(element);
  }

  /**
  * @generated
  */
  public ResourceSetInfo getResourceSetInfo(Object editorInput)
  {
    return (ResourceSetInfo) super.getElementInfo(editorInput);
  }

  /**
  * @generated
  */
  protected void disposeElementInfo(Object element, ElementInfo info)
  {
    if(info instanceof ResourceSetInfo)
    {
      ResourceSetInfo resourceSetInfo = (ResourceSetInfo) info;
      resourceSetInfo.dispose();
    }
    super.disposeElementInfo(element, info);
  }

  /**
  * @generated
  */
  protected void doValidateState(Object element, Object computationContext) throws CoreException
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      Collection files2Validate = new ArrayList();
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null && file.isReadOnly())
        {
          files2Validate.add(file);
        }
      }
      ResourcesPlugin.getWorkspace().validateEdit(
          (IFile[]) files2Validate.toArray(new IFile[files2Validate.size()]),
          computationContext);
    }

    super.doValidateState(element, computationContext);
  }

  /**
  * @generated
  */
  public boolean isReadOnly(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      if(info.isUpdateCache())
      {
        try
        {
          updateCache(element);
        }
        catch(CoreException ex)
        {
          StatemachineDiagramEditorPlugin.getInstance().logError(
              Messages.StatemachineDocumentProvider_isModifiable, ex);
          // Error message to log was initially taken from org.eclipse.gmf.runtime.diagram.ui.resources.editor.ide.internal.l10n.EditorMessages.StorageDocumentProvider_isModifiable
        }
      }
      return info.isReadOnly();
    }
    return super.isReadOnly(element);
  }

  /**
  * @generated
  */
  public boolean isModifiable(Object element)
  {
    if(!isStateValidated(element))
    {
      if(element instanceof FileEditorInput
          || element instanceof URIEditorInput)
      {
        return true;
      }
    }
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      if(info.isUpdateCache())
      {
        try
        {
          updateCache(element);
        }
        catch(CoreException ex)
        {
          StatemachineDiagramEditorPlugin.getInstance().logError(
              Messages.StatemachineDocumentProvider_isModifiable, ex);
          // Error message to log was initially taken from org.eclipse.gmf.runtime.diagram.ui.resources.editor.ide.internal.l10n.EditorMessages.StorageDocumentProvider_isModifiable
        }
      }
      return info.isModifiable();
    }
    return super.isModifiable(element);
  }

  /**
  * @generated
  */
  protected void updateCache(Object element) throws CoreException
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null && file.isReadOnly())
        {
          info.setReadOnly(true);
          info.setModifiable(false);
          return;
        }
      }
      info.setReadOnly(false);
      info.setModifiable(true);
      return;
    }
  }

  /**
  * @generated
  */
  protected void doUpdateStateCache(Object element) throws CoreException
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      info.setUpdateCache(true);
    }
    super.doUpdateStateCache(element);
  }

  /**
  * @generated
  */
  public boolean isSynchronized(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      return info.isSynchronized();
    }
    return super.isSynchronized(element);
  }

  /**
  * @generated
  */
  protected ISchedulingRule getResetRule(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      Collection rules = new ArrayList();
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null)
        {
          rules.add(ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(
              file));
        }
      }
      return new MultiRule((ISchedulingRule[]) rules
          .toArray(new ISchedulingRule[rules.size()]));
    }
    return null;
  }

  /**
  * @generated
  */
  protected ISchedulingRule getSaveRule(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      Collection rules = new ArrayList();
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null)
        {
          rules.add(computeSchedulingRule(file));
        }
      }
      return new MultiRule((ISchedulingRule[]) rules
          .toArray(new ISchedulingRule[rules.size()]));
    }
    return null;
  }

  /**
  * @generated
  */
  protected ISchedulingRule getSynchronizeRule(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      Collection rules = new ArrayList();
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null)
        {
          rules.add(ResourcesPlugin.getWorkspace().getRuleFactory()
              .refreshRule(file));
        }
      }
      return new MultiRule((ISchedulingRule[]) rules
          .toArray(new ISchedulingRule[rules.size()]));
    }
    return null;
  }

  /**
  * @generated
  */
  protected ISchedulingRule getValidateStateRule(Object element)
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      Collection files = new ArrayList();
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        IFile file = WorkspaceSynchronizer.getFile(nextResource);
        if(file != null)
        {
          files.add(file);
        }
      }
      return ResourcesPlugin.getWorkspace().getRuleFactory().validateEditRule(
          (IFile[]) files.toArray(new IFile[files.size()]));
    }
    return null;
  }

  /**
  * @generated
  */
  private ISchedulingRule computeSchedulingRule(IResource toCreateOrModify)
  {
    if(toCreateOrModify.exists())
      return ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(
          toCreateOrModify);

    IResource parent = toCreateOrModify;
    do
    {
      /*
       * XXX This is a workaround for
       * https://bugs.eclipse.org/bugs/show_bug.cgi?id=67601
       * IResourceRuleFactory.createRule should iterate the hierarchy
       * itself.
       */
      toCreateOrModify = parent;
      parent = toCreateOrModify.getParent();
    }
    while(parent != null && !parent.exists());

    return ResourcesPlugin.getWorkspace().getRuleFactory().createRule(
        toCreateOrModify);
  }

  /**
  * @generated
  */
  protected void doSynchronize(Object element, IProgressMonitor monitor) throws CoreException
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      for(Iterator it = info.getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource nextResource = (Resource) it.next();
        handleElementChanged(info, nextResource, monitor);
      }
      return;
    }
    super.doSynchronize(element, monitor);
  }

  /**
  * @generated
  */
  protected void doSaveDocument(IProgressMonitor monitor,
                                Object element,
                                IDocument document,
                                boolean overwrite) throws CoreException
  {
    ResourceSetInfo info = getResourceSetInfo(element);
    if(info != null)
    {
      if(!overwrite && !info.isSynchronized())
      {
        throw new CoreException(new Status(IStatus.ERROR,
            StatemachineDiagramEditorPlugin.ID,
            IResourceStatus.OUT_OF_SYNC_LOCAL,
            Messages.StatemachineDocumentProvider_UnsynchronizedFileSaveError,
            null));
      }
      info.stopResourceListening();
      fireElementStateChanging(element);
      List resources = info.getResourceSet().getResources();
      try
      {
        monitor.beginTask(
            Messages.StatemachineDocumentProvider_SaveDiagramTask, resources
                .size() + 1); //"Saving diagram"
        for(Iterator it = resources.iterator(); it.hasNext();)
        {
          Resource nextResource = (Resource) it.next();
          monitor.setTaskName(NLS.bind(
              Messages.StatemachineDocumentProvider_SaveNextResourceTask,
              nextResource.getURI()));
          if(nextResource.isLoaded()
              && !info.getEditingDomain().isReadOnly(nextResource))
          {
            try
            {
              nextResource.save(StatemachineDiagramEditorUtil.getSaveOptions());
            }
            catch(IOException e)
            {
              fireElementStateChangeFailed(element);
              throw new CoreException(new Status(IStatus.ERROR,
                  StatemachineDiagramEditorPlugin.ID,
                  EditorStatusCodes.RESOURCE_FAILURE, e.getLocalizedMessage(),
                  null));
            }
          }
          monitor.worked(1);
        }
        monitor.done();
        info.setModificationStamp(computeModificationStamp(info));
      }
      catch(RuntimeException x)
      {
        fireElementStateChangeFailed(element);
        throw x;
      }
      finally
      {
        info.startResourceListening();
      }
    }
    else
    {
      URI newResoruceURI;
      List affectedFiles = null;
      if(element instanceof FileEditorInput)
      {
        IFile newFile = ((FileEditorInput) element).getFile();
        affectedFiles = Collections.singletonList(newFile);
        newResoruceURI = URI.createPlatformResourceURI(newFile.getFullPath()
            .toString(), true);
      }
      else if(element instanceof URIEditorInput)
      {
        newResoruceURI = ((URIEditorInput) element).getURI();
      }
      else
      {
        fireElementStateChangeFailed(element);
        throw new CoreException(
            new Status(
                IStatus.ERROR,
                StatemachineDiagramEditorPlugin.ID,
                0,
                NLS
                    .bind(
                        Messages.StatemachineDocumentProvider_IncorrectInputError,
                        new Object[] {
                            element,
                            "org.eclipse.ui.part.FileEditorInput", "org.eclipse.emf.common.ui.URIEditorInput" }), //$NON-NLS-1$ //$NON-NLS-2$ 
                null));
      }
      if(false == document instanceof IDiagramDocument)
      {
        fireElementStateChangeFailed(element);
        throw new CoreException(
            new Status(
                IStatus.ERROR,
                StatemachineDiagramEditorPlugin.ID,
                0,
                "Incorrect document used: " + document + " instead of org.eclipse.gmf.runtime.diagram.ui.resources.editor.document.IDiagramDocument", null)); //$NON-NLS-1$ //$NON-NLS-2$
      }
      IDiagramDocument diagramDocument = (IDiagramDocument) document;
      final Resource newResource = diagramDocument.getEditingDomain()
          .getResourceSet().createResource(newResoruceURI);
      final Diagram diagramCopy = (Diagram) EcoreUtil.copy(diagramDocument
          .getDiagram());
      try
      {
        new AbstractTransactionalCommand(diagramDocument.getEditingDomain(),
            NLS.bind(Messages.StatemachineDocumentProvider_SaveAsOperation,
                diagramCopy.getName()), affectedFiles)
        {
          protected CommandResult doExecuteWithResult(IProgressMonitor monitor,
                                                      IAdaptable info) throws ExecutionException
          {
            newResource.getContents().add(diagramCopy);
            return CommandResult.newOKCommandResult();
          }
        }.execute(monitor, null);
        newResource.save(StatemachineDiagramEditorUtil.getSaveOptions());
      }
      catch(ExecutionException e)
      {
        fireElementStateChangeFailed(element);
        throw new CoreException(new Status(IStatus.ERROR,
            StatemachineDiagramEditorPlugin.ID, 0, e.getLocalizedMessage(),
            null));
      }
      catch(IOException e)
      {
        fireElementStateChangeFailed(element);
        throw new CoreException(new Status(IStatus.ERROR,
            StatemachineDiagramEditorPlugin.ID, 0, e.getLocalizedMessage(),
            null));
      }
      newResource.unload();
    }
  }

  /**
  * @generated
  */
  protected void handleElementChanged(ResourceSetInfo info,
                                      Resource changedResource,
                                      IProgressMonitor monitor)
  {
    IFile file = WorkspaceSynchronizer.getFile(changedResource);
    if(file != null)
    {
      try
      {
        file.refreshLocal(IResource.DEPTH_INFINITE, monitor);
      }
      catch(CoreException ex)
      {
        StatemachineDiagramEditorPlugin.getInstance().logError(
            Messages.StatemachineDocumentProvider_handleElementContentChanged,
            ex);
        // Error message to log was initially taken from org.eclipse.gmf.runtime.diagram.ui.resources.editor.ide.internal.l10n.EditorMessages.FileDocumentProvider_handleElementContentChanged
      }
    }
    changedResource.unload();

    fireElementContentAboutToBeReplaced(info.getEditorInput());
    removeUnchangedElementListeners(info.getEditorInput(), info);
    info.fStatus = null;
    try
    {
      setDocumentContent(info.fDocument, info.getEditorInput());
    }
    catch(CoreException e)
    {
      info.fStatus = e.getStatus();
    }
    if(!info.fCanBeSaved)
    {
      info.setModificationStamp(computeModificationStamp(info));
    }
    addUnchangedElementListeners(info.getEditorInput(), info);
    fireElementContentReplaced(info.getEditorInput());
  }

  /**
  * @generated
  */
  protected void handleElementMoved(IEditorInput input, URI uri)
  {
    if(input instanceof FileEditorInput)
    {
      IFile newFile = ResourcesPlugin.getWorkspace().getRoot().getFile(
          new Path(URI.decode(uri.path())).removeFirstSegments(1));
      fireElementMoved(input, newFile == null ? null : new FileEditorInput(
          newFile));
      return;
    }
    // TODO: append suffix to the URI! (use diagram as a parameter)
    fireElementMoved(input, new URIEditorInput(uri));
  }

  /**
  * @generated
  */
  public IEditorInput createInputWithEditingDomain(IEditorInput editorInput,
                                                   TransactionalEditingDomain domain)
  {
    return editorInput;
  }

  /**
  * @generated
  */
  public IDiagramDocument getDiagramDocument(Object element)
  {
    IDocument doc = getDocument(element);
    if(doc instanceof IDiagramDocument)
    {
      return (IDiagramDocument) doc;
    }
    return null;
  }

  /**
  * @generated
  */
  protected IRunnableContext getOperationRunner(IProgressMonitor monitor)
  {
    return null;
  }

  /**
  * @generated
  */
  protected class ResourceSetInfo extends ElementInfo
  {

    /**
    * @generated
    */
    private long myModificationStamp = IResource.NULL_STAMP;

    /**
    * @generated
    */
    private WorkspaceSynchronizer mySynchronizer;

    /**
    * @generated
    */
    private Collection myUnSynchronizedResources = new ArrayList();

    /**
    * @generated
    */
    private IDiagramDocument myDocument;

    /**
    * @generated
    */
    private IEditorInput myEditorInput;

    /**
    * @generated
    */
    private boolean myUpdateCache = true;

    /**
    * @generated
    */
    private boolean myModifiable = false;

    /**
    * @generated
    */
    private boolean myReadOnly = true;

    /**
    * @generated
    */
    private ResourceSetModificationListener myResourceSetListener;

    /**
    * @generated
    */
    public ResourceSetInfo(IDiagramDocument document, IEditorInput editorInput)
    {
      super(document);
      myDocument = document;
      myEditorInput = editorInput;
      startResourceListening();
      myResourceSetListener = new ResourceSetModificationListener(this);
      getResourceSet().eAdapters().add(myResourceSetListener);
    }

    /**
    * @generated
    */
    public long getModificationStamp()
    {
      return myModificationStamp;
    }

    /**
    * @generated
    */
    public void setModificationStamp(long modificationStamp)
    {
      myModificationStamp = modificationStamp;
    }

    /**
    * @generated
    */
    public TransactionalEditingDomain getEditingDomain()
    {
      return myDocument.getEditingDomain();
    }

    /**
    * @generated
    */
    public ResourceSet getResourceSet()
    {
      return getEditingDomain().getResourceSet();
    }

    /**
    * @generated
    */
    public IEditorInput getEditorInput()
    {
      return myEditorInput;
    }

    /**
    * @generated
    */
    public void dispose()
    {
      stopResourceListening();
      getResourceSet().eAdapters().remove(myResourceSetListener);
      for(Iterator it = getResourceSet().getResources().iterator(); it
          .hasNext();)
      {
        Resource resource = (Resource) it.next();
        resource.unload();
      }
    }

    /**
    * @generated
    */
    public boolean isSynchronized()
    {
      return myUnSynchronizedResources.size() == 0;
    }

    /**
    * @generated
    */
    public void setUnSynchronized(Resource resource)
    {
      myUnSynchronizedResources.add(resource);
    }

    /**
    * @generated
    */
    public void setSynchronized(Resource resource)
    {
      myUnSynchronizedResources.remove(resource);
    }

    /**
    * @generated
    */
    public final void stopResourceListening()
    {
      mySynchronizer.dispose();
      mySynchronizer = null;
    }

    /**
    * @generated
    */
    public final void startResourceListening()
    {
      mySynchronizer = new WorkspaceSynchronizer(getEditingDomain(),
          new SynchronizerDelegate());
    }

    /**
    * @generated
    */
    public boolean isUpdateCache()
    {
      return myUpdateCache;
    }

    /**
    * @generated
    */
    public void setUpdateCache(boolean update)
    {
      myUpdateCache = update;
    }

    /**
    * @generated
    */
    public boolean isModifiable()
    {
      return myModifiable;
    }

    /**
    * @generated
    */
    public void setModifiable(boolean modifiable)
    {
      myModifiable = modifiable;
    }

    /**
    * @generated
    */
    public boolean isReadOnly()
    {
      return myReadOnly;
    }

    /**
    * @generated
    */
    public void setReadOnly(boolean readOnly)
    {
      myReadOnly = readOnly;
    }

    /**
    * @generated
    */
    private class SynchronizerDelegate implements
        WorkspaceSynchronizer.Delegate
    {

      /**
      * @generated
      */
      public void dispose()
      {
      }

      /**
      * @generated
      */
      public boolean handleResourceChanged(final Resource resource)
      {
        synchronized(ResourceSetInfo.this)
        {
          if(ResourceSetInfo.this.fCanBeSaved)
          {
            ResourceSetInfo.this.setUnSynchronized(resource);
            return true;
          }
        }
        Display.getDefault().asyncExec(new Runnable()
        {
          public void run()
          {
            handleElementChanged(ResourceSetInfo.this, resource, null);
          }
        });
        return true;
      }

      /**
      * @generated
      */
      public boolean handleResourceDeleted(Resource resource)
      {
        synchronized(ResourceSetInfo.this)
        {
          if(ResourceSetInfo.this.fCanBeSaved)
          {
            ResourceSetInfo.this.setUnSynchronized(resource);
            return true;
          }
        }
        Display.getDefault().asyncExec(new Runnable()
        {
          public void run()
          {
            fireElementDeleted(ResourceSetInfo.this.getEditorInput());
          }
        });
        return true;
      }

      /**
      * @generated
      */
      public boolean handleResourceMoved(Resource resource, final URI newURI)
      {
        synchronized(ResourceSetInfo.this)
        {
          if(ResourceSetInfo.this.fCanBeSaved)
          {
            ResourceSetInfo.this.setUnSynchronized(resource);
            return true;
          }
        }
        if(myDocument.getDiagram().eResource() == resource)
        {
          Display.getDefault().asyncExec(new Runnable()
          {
            public void run()
            {
              handleElementMoved(ResourceSetInfo.this.getEditorInput(), newURI);
            }
          });
        }
        else
        {
          handleResourceDeleted(resource);
        }
        return true;
      }

    }

  }

  /**
  * @generated
  */
  private class ResourceSetModificationListener extends EContentAdapter
  {

    /**
    * @generated
    */
    private NotificationFilter myModifiedFilter;

    /**
    * @generated
    */
    private ResourceSetInfo myInfo;

    /**
    * @generated
    */
    public ResourceSetModificationListener(ResourceSetInfo info)
    {
      myInfo = info;
      myModifiedFilter = NotificationFilter.createEventTypeFilter(
          Notification.SET).or(
          NotificationFilter.createEventTypeFilter(Notification.UNSET)).and(
          NotificationFilter.createFeatureFilter(Resource.class,
              Resource.RESOURCE__IS_MODIFIED));
    }

    /**
    * @generated
    */
    public void notifyChanged(Notification notification)
    {
      if(notification.getNotifier() instanceof ResourceSet)
      {
        super.notifyChanged(notification);
      }
      if(!notification.isTouch() && myModifiedFilter.matches(notification))
      {
        if(notification.getNotifier() instanceof Resource)
        {
          Resource resource = (Resource) notification.getNotifier();
          if(resource.isLoaded())
          {
            boolean modified = false;
            for(Iterator it = myInfo.getResourceSet().getResources().iterator(); it
                .hasNext()
                && !modified;)
            {
              Resource nextResource = (Resource) it.next();
              if(nextResource.isLoaded())
              {
                modified = nextResource.isModified();
              }
            }
            boolean dirtyStateChanged = false;
            synchronized(myInfo)
            {
              if(modified != myInfo.fCanBeSaved)
              {
                myInfo.fCanBeSaved = modified;
                dirtyStateChanged = true;
              }
              if(!resource.isModified())
              {
                myInfo.setSynchronized(resource);
              }
            }
            if(dirtyStateChanged)
            {
              fireElementDirtyStateChanged(myInfo.getEditorInput(), modified);

              if(!modified)
              {
                myInfo.setModificationStamp(computeModificationStamp(myInfo));
              }
            }
          }
        }
      }
    }

  }

}
